/*
 * spi_flash_W25Q64.c
 *
 *  Created on: Oct 19, 2022
 *      Author: yu
 */

#include "spi_flash_W25Q64.h"
#include "gpio.h"
#include "spi.h"

#define INSTX_WRITE_ENABLE          0x06
#define INSTX_JEDEC_ID              0x9F
#define INSTX_READ_DATA             0x03
#define INSTX_PAGE_PROGRAM          0x02
#define INSTX_SECTOR_ERASE          0x20
#define INSTX_BLOCK_ERASE_32KB      0x52
#define INSTX_BLOCK_ERASE_64KB      0xD8
#define INSTX_CHIP_ERASE            0xC7
#define INSTX_READ_STATUS1          0x05

#define BUSY_BIT_MASK               0x01

#define SPI_FLASH_CS_LO()           HAL_GPIO_WritePin(SPI_FLASH_CS_GPIO_Port, SPI_FLASH_CS_Pin, GPIO_PIN_RESET)
#define SPI_FLASH_CS_HI()           HAL_GPIO_WritePin(SPI_FLASH_CS_GPIO_Port, SPI_FLASH_CS_Pin, GPIO_PIN_SET)

#define SPI_FLASH_PAGE_BYTES        256
#define SPI_FLASH_SECTOR_BYTES      (SPI_FLASH_PAGE_BYTES*16) //4KB
#define SPI_FLASH_BLOCK_BYTES       (SPI_FLASH_SECTOR_BYTES*16) //64KB

//-private code------------------------------------------------------------------
static uint8_t s_u8ReadStatus1(void);
static void s_WaitForWriteEnd(void);
static void s_WriteEnable(void);
static void s_PageProgram(uint8_t *pWrData, uint32_t u32Addr, uint16_t u16BytesNum);

/********************************************************************************
*函数功能: 读取STATUS 1，
* busy status is set to 1 when executing a page program, sector erase, block erase,
* chip erase, write status register or erase/program security register instruction
*修改记录:
*日期:                             作者:                             摘要:
      2022年10月21日      YummoShin         Create
********************************************************************************/
static uint8_t s_u8ReadStatus1(void)
{
  uint8_t u8Status;
  uint8_t u8TxData=INSTX_READ_STATUS1;

  SPI_FLASH_CS_LO();
  HAL_SPI_TransmitReceive(&hspi1, &u8TxData, &u8Status, 1, 10);
  SPI_FLASH_CS_HI();

  return u8Status;
}

/********************************************************************************
*函数功能: 等待写BUSY位清零
* busy status is set to 1 when executing a page program, sector erase, block erase,
* chip erase, write status register or erase/program security register instruction
*修改记录:
*日期:                             作者:                             摘要:
      2022年10月21日      YummoShin         Create
********************************************************************************/
static void s_WaitForWriteEnd(void)
{
  uint8_t u8Status;
  uint8_t u8TxData=INSTX_READ_STATUS1;

  SPI_FLASH_CS_LO();
  HAL_SPI_Transmit(&hspi1, &u8TxData, 1, 10);
  do
  {
    HAL_SPI_Receive(&hspi1, &u8Status, 1, 10);
  }while((u8Status&BUSY_BIT_MASK)==BUSY_BIT_MASK);
  SPI_FLASH_CS_HI();
}

/********************************************************************************
*函数功能: write enable
* A write disable state occurs upon power-up or after any of the following instructions:
* Write Disable, Page Program, Quad Page Program, Sector Erase, Block Erase,
* Chip Erase, Write Status Register, Erase Security Register and
* Program Security Register.
*修改记录:
*日期:                             作者:                             摘要:
      Oct 21, 2022      YummoShin         Create
********************************************************************************/
static void s_WriteEnable(void)
{
  uint8_t u8TxData=INSTX_WRITE_ENABLE;

  SPI_FLASH_CS_LO();
  HAL_SPI_Transmit(&hspi1, &u8TxData, 1, 10);
  SPI_FLASH_CS_HI();
}

/********************************************************************************
*函数功能: 读取JEDEC ID
*修改记录:
*日期:                             作者:                             摘要:
      Oct 21, 2022      YummoShin         Create
********************************************************************************/
uint32_t u32SPI_Flash_ReadJedecId(void)
{
  uint32_t u32JedecId;
  uint8_t u8RxData[3], TxData=INSTX_JEDEC_ID;

  SPI_FLASH_CS_LO();
  HAL_SPI_Transmit(&hspi1, &TxData, 1, 10);
  HAL_SPI_Receive(&hspi1, u8RxData, 3, 30);
  SPI_FLASH_CS_HI();

  u32JedecId = u8RxData[0]<<16 | u8RxData[1]<<8 | u8RxData[2];

  return u32JedecId;
}

/********************************************************************************
*函数功能: 读取数据
*修改记录:
*日期:                             作者:                             摘要:
      2022年10月23日      YummoShin         Create
********************************************************************************/
void SPI_Flash_ReadData(uint8_t *pRdData, uint32_t u32Addr, uint32_t u32BytesNum)
{
  uint8_t u8TxData[4];

  u8TxData[0] = INSTX_READ_DATA;
  u8TxData[1] = (u32Addr&0xFF0000)>>16;
  u8TxData[2] = (u32Addr&0xFF00)>>8;
  u8TxData[3] = u32Addr&0xFF;

  SPI_FLASH_CS_LO();
  HAL_SPI_Transmit(&hspi1, u8TxData, 4, 40);
  HAL_SPI_Receive(&hspi1, pRdData, u32BytesNum, HAL_MAX_DELAY);
  SPI_FLASH_CS_HI();
}

/********************************************************************************
*函数功能: 页写入命令，写入字节需要小于等于256
*修改记录:
*日期:                             作者:                             摘要:
      2022年10月23日      YummoShin         Create
********************************************************************************/
static void s_PageProgram(uint8_t *pWrData, uint32_t u32Addr, uint16_t u16BytesNum)
{
  uint8_t u8TxData[4];

  u8TxData[0] = INSTX_PAGE_PROGRAM;
  u8TxData[1] = (u32Addr&0xFF0000)>>16;
  u8TxData[2] = (u32Addr&0xFF00)>>8;
  u8TxData[3] = u32Addr&0xFF;

  if(u16BytesNum>SPI_FLASH_PAGE_BYTES)
  {
    u16BytesNum = SPI_FLASH_PAGE_BYTES;
  }

  s_WriteEnable();

  SPI_FLASH_CS_LO();
  HAL_SPI_Transmit(&hspi1, u8TxData, 4, 40);
  HAL_SPI_Transmit(&hspi1, pWrData, u16BytesNum, HAL_MAX_DELAY);
  SPI_FLASH_CS_HI();

  s_WaitForWriteEnd();
}

/********************************************************************************
*函数功能: 写入数据，与FLASH PAGE BYTES 对齐，写入前要先擦除
*修改记录:
*日期:                             作者:                             摘要:
      2022年10月23日      YummoShin         Create
********************************************************************************/
void SPI_FLASH_WriteData(uint8_t *pWrData, uint32_t u32Addr, uint32_t u32BytesNum)
{
  uint32_t u32BytesToWrite=0;

  //先处理一页以及一页内的数据
  u32BytesToWrite = SPI_FLASH_PAGE_BYTES-(u32Addr%SPI_FLASH_PAGE_BYTES);
  if(u32BytesToWrite>u32BytesNum)
  {
    u32BytesToWrite = u32BytesNum;
  }
  s_PageProgram(pWrData, u32Addr, u32BytesToWrite);

  u32Addr += u32BytesToWrite;
  pWrData += u32BytesToWrite;
  u32BytesNum -= u32BytesToWrite;

  //有剩余页数处理
  if(u32BytesNum > 0)
  {
    uint32_t u32Pages=u32BytesNum/SPI_FLASH_PAGE_BYTES;
    uint32_t u32BytesLeft=u32BytesNum%SPI_FLASH_PAGE_BYTES;

    for(; u32Pages>0; u32Pages--)
    {
      u32BytesToWrite = SPI_FLASH_PAGE_BYTES;
      s_PageProgram(pWrData, u32Addr, u32BytesToWrite);
      u32Addr += u32BytesToWrite;
      pWrData += u32BytesToWrite;
      u32BytesNum -= u32BytesToWrite;
    }

    if(u32BytesLeft>0)
    {
      u32BytesToWrite = u32BytesLeft;
      s_PageProgram(pWrData, u32Addr, u32BytesToWrite);
      u32Addr += u32BytesToWrite;
      pWrData += u32BytesToWrite;
      u32BytesNum -= u32BytesToWrite;
    }
  }
}

/********************************************************************************
*函数功能: 擦除扇区，对齐到4K
*修改记录:
*日期:                             作者:                             摘要:
      2022年10月23日      YummoShin         Create
********************************************************************************/
void SPI_FLASH_SectorErase(uint32_t u32SectorAddr)
{
  uint8_t u8TxData[4];

  s_WriteEnable();

  u8TxData[0] = INSTX_SECTOR_ERASE;
  u8TxData[1] = (u32SectorAddr&0xFF0000)>>16;
  u8TxData[2] = (u32SectorAddr&0xFF00)>>8;
  u8TxData[3] = u32SectorAddr&0xFF;

  SPI_FLASH_CS_LO();
  HAL_SPI_Transmit(&hspi1, u8TxData, 4, 40);
  SPI_FLASH_CS_HI();

  s_WaitForWriteEnd();
}

/********************************************************************************
*函数功能: 擦除块，对齐到64K
*修改记录:
*日期:                             作者:                             摘要:
      2022年10月23日      YummoShin         Create
********************************************************************************/
void SPI_FLASH_BlockErase(uint32_t u32BlockAddr)
{
  uint8_t u8TxData[4];

  s_WriteEnable();

  u8TxData[0] = INSTX_BLOCK_ERASE_64KB;
  u8TxData[1] = (u32BlockAddr&0xFF0000)>>16;
  u8TxData[2] = (u32BlockAddr&0xFF00)>>8;
  u8TxData[3] = u32BlockAddr&0xFF;

  SPI_FLASH_CS_LO();
  HAL_SPI_Transmit(&hspi1, u8TxData, 4, 40);
  SPI_FLASH_CS_HI();

  s_WaitForWriteEnd();
}

/********************************************************************************
*函数功能: 全片擦除
*修改记录:
*日期:                             作者:                             摘要:
      2022年10月23日      YummoShin         Create
********************************************************************************/
void SPI_FLASH_ChipErase(void)
{
  uint8_t u8TxData;

  s_WriteEnable();

  u8TxData = INSTX_CHIP_ERASE;

  SPI_FLASH_CS_LO();
  HAL_SPI_Transmit(&hspi1, &u8TxData, 1, 10);
  SPI_FLASH_CS_HI();

  s_WaitForWriteEnd();
}
